﻿// project      : soapi-notify 
// purpose      : console and growl notifications of new Stack Exchange questions
// source       : http://soapi.info/tools/notify.aspx
// announcement : http://stackapps.com/questions/820
// website      : http://soapi.info/tools/notify.aspx
// license      : MIT/X11 - Copyright (c) 2010 Sky Sanders - See License.txt
// inspiration  : http://stackapps.com/questions/817/senotify-se-new-question-notification
// 
//  
using System;
using System.IO;
using System.Reflection;
using Growl.Connector;
using Growl.CoreLibrary;
using Soapi.Notify.Properties;

namespace Soapi.Notify
{
    /// <summary>
    ///   A hack of a class to ensure that the displays are always synced up with the version
    ///   of sonotify.
    /// </summary>
    public static class Installer
    {
        private const string ResourcePathMini = "Soapi.Notify.Displays.soapi.notify.displays.mini.soapi.notify.displays.mini.dll";
        private const string ResourcePathDefault = "Soapi.Notify.Displays.soapi.notify.displays.default.soapi.notify.displays.default.dll";
        private const string DisplayFolderMini = "soapi-notify-mini";
        private const string DisplayFolderDefault = "soapi-notify-default";
        private const string BinNameMini = "soapi.notify.displays.mini.dll";
        private const string BinNameDefault = "soapi.notify.displays.default.dll";

        public static void RegisterApp()
        {
            var growlNotificationType = new NotificationType(Program.ApplicationName, Program.ApplicationName,
                                              Resources.logic_or.ToBitmap(), true) { DisplayName = "soapi-notify" };
            var growlConnector = new GrowlConnector
            {
                EncryptionAlgorithm = Cryptography.SymmetricAlgorithmType.PlainText
            };
            var growlApplication = new Application(Program.ApplicationName);
            growlConnector.Register(growlApplication, new[] { growlNotificationType });
        }

        public static bool GrowlInstalled
        {
            get
            {
                bool isInstalled = new Detector().IsInstalled;
                if(isInstalled)
                {
                    RegisterApp();
                }
                return isInstalled;
            }
        }
        public static bool GrowlIsRunning
        {
            get
            {
                return new Detector().IsGrowlRunning;
            }
        }

        public static string DisplayPath
        {

            get
            {
                string baseDisplayPath = Path.Combine(
                                 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                 @"Growl\2.0.0.0\Displays");

                return baseDisplayPath;
            }
        }
        public static bool DisplaysInstalled
        {
            get
            {
                return File.Exists(Path.Combine(DisplayPath, Path.Combine(DisplayFolderDefault, BinNameDefault)));

            }
        }
        public static bool DisplaysCurrent
        {
            get
            {
                string displayBinPath = Path.Combine(DisplayPath, Path.Combine(DisplayFolderDefault, BinNameDefault));
                bool displaysCurrent = false;
                if (DisplaysInstalled)
                {
                    Assembly assembly = typeof(Installer).Assembly;
                    AssemblyName assemblyName = AssemblyName.GetAssemblyName(displayBinPath);
                    displaysCurrent = assemblyName.Version.ToString() == assembly.GetName().Version.ToString();

                }

                return displaysCurrent;
            }
        }

        public static void InstallDisplays()
        {
            var detector = new Detector();
            if (detector.IsInstalled)
            {
                Assembly assembly = typeof(Installer).Assembly;

                byte[] miniDisplay = assembly.GetManifestResourceStream(ResourcePathMini).ReadFully();
                byte[] defaultDisplay = assembly.GetManifestResourceStream(ResourcePathDefault).ReadFully();

                InstallDisplay(defaultDisplay, DisplayFolderDefault, BinNameDefault);
                InstallDisplay(miniDisplay, DisplayFolderMini, BinNameMini);
            }
            else
            {
                // growl was not detected on this machine
                throw new Exception(
                    "The Growl Display Installer requires that Growl for Windows already be installed on the system.");
            }
        }


        private static void InstallDisplay(byte[] bin, string binDirectoryName, string binFileName)
        {
            var detector = new Detector();

            Assembly assembly = typeof (Installer).Assembly;

            string displayFolder = Path.Combine(DisplayPath, binDirectoryName);


            if (!Directory.Exists(displayFolder))
            {
                Console.WriteLine("Creating display folder: {0}", displayFolder);
                Directory.CreateDirectory(displayFolder);
            }

            string displayBinPath = Path.Combine(displayFolder, binFileName);

            byte[] growlCoreBin =
                assembly.GetManifestResourceStream(
                    "Soapi.Notify.Displays.soapi.notify.displays.default.Growl.CoreLibrary.dll").ReadFully();

            byte[] growlDisplayBin =
                assembly.GetManifestResourceStream(
                    "Soapi.Notify.Displays.soapi.notify.displays.default.Growl.DisplayStyle.dll").ReadFully();

            try // catch file access exception and notify user to shut down growl, rerun with -g, then restart growl
            {
                Console.WriteLine("Writing display files: {0}", binDirectoryName);
                File.WriteAllBytes(Path.Combine(displayFolder, "Growl.CoreLibrary.dll"), growlCoreBin);
                File.WriteAllBytes(Path.Combine(displayFolder, "Growl.DisplayStyle.dll"), growlDisplayBin);
                File.WriteAllBytes(displayBinPath, bin);
            }
            catch (Exception ex)
            {
                if (detector.IsGrowlRunning)
                {
                    throw new Exception(
                        "Displays cannot be upgraded while Growl is running.\r\nPlease ensure that Growl is not running and repeat this process.");
                }
                throw;
            }
        }

        /// <summary>
        ///   Reads data from a stream until the end is reached. The
        ///   data is returned as a byte array. An IOException is
        ///   thrown if any of the underlying IO calls fail.
        /// </summary>
        /// <param name = "stream">The stream to read data from</param>
        public static byte[] ReadFully(this Stream stream)
        {
            byte[] buffer = new byte[32768];
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    int read = stream.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                        return ms.ToArray();
                    ms.Write(buffer, 0, read);
                }
            }
        }
    }
}